package com.ymx.darling.mapper.proxy;

import com.ymx.darling.bean.entity.BeanInfo;
import com.ymx.darling.bean.entity.BeanInfoManager;
import com.ymx.darling.build.mapper.MapperMethodInfoInfoManager;
import com.ymx.darling.build.mapper.method.MapperMethodInfo;
import com.ymx.darling.log.LogFactory;
import com.ymx.darling.mapper.*;
import com.ymx.darling.mapper.annotation.ProviderInvoker;
import com.ymx.darling.mapper.annotation.Select;
import com.ymx.darling.mapper.annotation.Update;
import com.ymx.darling.session.Session;
import com.ymx.darling.session.setting.Context;
import com.ymx.darling.transaction.Close;
import com.ymx.darling.util.ExceptionUtil;
import com.ymx.darling.util.ObjectUtil;
import com.ymx.darling.util.ReflectUtil;
import com.ymx.darling.util.StringUtil;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Mapper代理总控制器
 * @author 爱Java的小于
 */
public class MapperProxyInvocationHandler implements InvocationHandler {
    /**
     * 命名空间
     */
    private final Class<?> namespace;
    /**
     * 代理实例
     */
    private EntityMapper<?> entityMapper;
    /**
     * session实例
     */
    private final Session session;
    /**
     * MapperInfo管理器
     */
    private final MapperMethodInfoRepository methodInfoRepository;
    /**
     * 存储扩展方法中是否被重写的状态
     */
    private static final Map<String, Boolean> isProviderMethodCache = new ConcurrentHashMap<>();

    /**
     * 代理控制器有参构造
     * @param session session实例
     * @param methodInfoRepository mapperMethodInfo集合
     * @param mapperInterface 被代理接口的Class
     */
    public MapperProxyInvocationHandler(Session session, MapperMethodInfoRepository methodInfoRepository, Class<?> mapperInterface){
        this.session = session;
        this.namespace = mapperInterface;
        this.methodInfoRepository = methodInfoRepository;
        //判断当前生成代理对象的接口类型继承EntityMapper那么创建一个EntityMapper目标代理实例
        if (EntityMapper.class.isAssignableFrom(mapperInterface)) {
            Class<?> entityType = MapperMethodInfoInfoManager.getEntityType(mapperInterface);
            BeanInfo beanInfo = BeanInfoManager.getBeanInfo(entityType);
            this.entityMapper = new DefaultEntityMapper<>(session, beanInfo);
        }
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (this.isSessionMethod(method.getDeclaringClass())) {
            return method.invoke(this.session, args);
        } else {
            try {
                return this.cacheInvoker(proxy, method, args);
            } catch (Throwable throwable) {
                throw ExceptionUtil.unwrapThrowable(throwable);
            } finally {
                Context.clearSetting();
            }
        }
    }


    /**
     * 缓存执行sql方法
     * @param proxy 代理对象
     * @param method 方法
     * @param args 参数
     * @return Object
     * @throws Throwable 抛出的异常
     */
    private Object cacheInvoker(Object proxy, Method method, Object[] args) throws Throwable {
        // 为当前执行的sql设置一个mapper方法的上下文信息
        Context.setMapperMethod(method);
        Context.setNamespace(this.namespace);
        String mapperMethodInfoKey = StringUtil.createStatementIdWithMapperMethod();
        Context.setStatementLog(LogFactory.getLog(mapperMethodInfoKey));

        // 判断调用的是扩展Mapper(EntityMapper, SqlMapper, CallableMapper)中的方法
        if (ReflectUtil.isExtendsMethod(method) && !this.extendsMethodIsOverrideMethod(method, mapperMethodInfoKey)) {
            Method declaredMethod = ReflectUtil.getExtendsMethod(method);
            Class<?> declaringClass = declaredMethod.getDeclaringClass();
            if (declaringClass == EntityMapper.class) {
                return declaredMethod.invoke(this.entityMapper, args);
            }
            return declaredMethod.invoke(this.session, args);

        // 判断调用default修饰的方法
        } else if (this.isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);

        // 调用用户自定义sql的方法
        } else {
            MethodInvoker methodInvoker = this.methodInfoRepository.get(mapperMethodInfoKey);
            Object result = methodInvoker.invoke(this.session, mapperMethodInfoKey, args);
            // 处理自定义方法的返回值容器类型和框架默认的结果集容器类型不一致问题
            if (methodInvoker instanceof MapperMethodInfo) {
                MapperMethodInfo mapperMethodInfo = (MapperMethodInfo) methodInvoker;
                if (ObjectUtil.isListOrMap(mapperMethodInfo.getMethodReturnCollectionType())) {
                    result = this.adaptCollection(mapperMethodInfo.getMethodReturnCollectionType(), result);
                }
            }
            return result;
        }
    }


    /**
     * 保证List与Map同方法返回值兼容
     * @param collectionResult result
     * @param collectionType 指定的集合类型
     * @return Object
     */
    private Object adaptCollection(Class<?> collectionType, Object collectionResult) {
        if (!collectionType.isAssignableFrom(collectionResult.getClass())) {
            if (List.class.isAssignableFrom(collectionType)) {
                List<Object> resultList = ReflectUtil.newInstanceList(collectionType);
                List<?> reList = (List<?>) collectionResult;
                resultList.addAll(reList);
                return resultList;
            } else {
                Map<Object, Object> resultMap = ReflectUtil.newInstanceMap(collectionType);
                Map<?,?> reMap = (Map<?, ?>) collectionResult;
                resultMap.putAll(reMap);
                return resultMap;
            }
        } else {
            return collectionResult;
        }
    }


    /**
     * 判断自定义方法是否覆盖了继承的方法
     * @param method 自定义方法
     * @param methodKey 自定义方法id
     */
    private boolean extendsMethodIsOverrideMethod(Method method, String methodKey) {
        return isProviderMethodCache.computeIfAbsent(methodKey, key ->
                method.isAnnotationPresent(ProviderInvoker.class) ||
                method.isAnnotationPresent(Select.class) ||
                method.isAnnotationPresent(Update.class));
    }


    /**
     * 判断类型是否是扩展类
     * @param type type
     * @return boo
     */
    private boolean isSessionMethod(Class<?> type) {
        return type == Session.class || type == Close.class || type == Object.class;
    }


    /**
     * 执行默认方法
     * @param proxy proxy
     * @param method method
     * @param args args
     * @return Object
     */
    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
        Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }

        Class<?> declaringClass = method.getDeclaringClass();
        return constructor.newInstance(declaringClass, 15).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }


    /**
     * 判断是否是默认方法
     * @param method method
     * @return boo
     */
    private boolean isDefaultMethod(Method method) {
        return (method.getModifiers() & 1033) == 1 && method.getDeclaringClass().isInterface();
    }
}
